延續昨天的工作,我們最後的工作就是要將這些拼圖湊在一起,完成我們的模組。
先看看我們現在有了什麼:
其實關於實體還有很多的功能可以自定義,我們這裡只需要關心自定義實體的大小就好。
Minecraft一定是先載入模組與所有資源,因此註冊事件與註冊實體類別這些事情一定要先完成。我們昨天已經將實體註冊好,還缺將事件類別註冊:
@EventHandler
public void preInit(FMLPreInitializationEvent event) {
...略
}
@EventHandler
public void init(FMLInitializationEvent event) {
MinecraftForge.EVENT_BUS.register(new PigDoll_v2());
}
同樣的,不需要的事件就取消註冊,這邊只保留PigDoll_V2
。
再來,當豬實體加入世界後(EntityJoinWorldEvent
),我們需要產生新的豬實體,並且是使用我們的EntityScalePig
類別,這樣之後我們才可以更改豬的大小
@SubscribeEvent
public void pigDoll(EntityJoinWorldEvent event) {
if (EntityPig.class.isAssignableFrom(event.entity.getClass())) {
EntityScalePig pig = new EntityScalePig(event.entity.worldObj);
event.entity.worldObj.spawnEntityInWorld(pig);
}
}
這裡使用另一種方法來判斷今天加入到世界的實體是不是"豬" - isAssignableFrom()
從左至右的意思是"EntityPig
是不是與event.entity
的類別相同,或是EntityPig是被event.entity繼承的類別"。
因為第一次呼叫這個方法的時候,event.entity一定是一般的豬實體(EntityPig
);只有在我們透過spawnEntityInWorld
方法加入自定義EntityScalePig
實體後,會再次觸發實體加入世界的事件 - 這時候event.entity取得的就是自定義大小的豬實體EntityPig
。我們的行為是"1個EntityPig要產生多個EntityScalePig",所以這裡要用isAssignableFrom
來接受兩種類別都可以產生新的豬。
你可以發現到這裡只能用IF,不能用For迴圈來完成我們的工作。因為EntityJoinWorldEvent觸發的其中一個條件就是spawnEntityInWorld
方法,而用For迴圈會造成無窮呼叫!
這個觀念很重要,事件的發生會被所有的訂閱者 - 即事件類別 - 所看到並處理
恩...覺得我有點像老人家廢話有點多,我們繼續下面一個步驟好了。
實體產生後,我們要變更兩個大小:碰撞箱
與渲染大小
。這兩個動作只能在"客戶端"進行 (因為它是畫面產生的動作,不是固定的物件 - 你總不能仰賴伺服器端對每一個連線的客戶端都幫忙將畫面產生好吧?),所以我們需要昨天提到的@SideOnly(Side.CLIENT)
註釋。另外,有一個事件是專門處理實體(Event),或更精確地說 - 活體(Living)的預先渲染動作,RenderLivingEvent.Pre
。我們在PigDoll_v2類別內加入一個新的方法preRender
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void preRender(RenderLivingEvent.Pre event) {
}
在上面的新方法內,我們要加上兩種大小的變更:
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void preRender(RenderLivingEvent.Pre event) {
if(event.renderer instanceof RenderScalePig) {
RenderScalePig render = (RenderScalePig) event.renderer;
render.setScale(parserScale);
}
if (event.entity instanceof EntityScalePig)) {
EntityScalePig pig = (EntityScalePig) event.entity;
pig.scale(parserScale);
}
}
這裡的會產生紅字parserScale
不存在,我們先不管它。先看第一個IF判斷式,在預渲染事件中,當渲染的物件event.renderer是我們自定義的渲染類別RenderScalePig
時,做一個強制轉換的動作 (就是這邊的RenderScalePig) event.render
),因為有先判斷過了,所以這裡的強制轉換是安全的;接下來透過我們定義好的render.setScale()
方法來改變渲染的大小比例是parserScale。
後半段是將event.entity強制轉換成自定義的實體類別EntityScalePig
,然後透過定義的pig.scale()
改變碰撞箱大小比例是parserScale。
parseScale是我們要動態改變的比例,這個參數我們可以透過event.entity.getCustomNameTag()
來取得自定義的實體名稱,然後在實體名稱裡面加上我們想要的比例,因此名稱為:myPig-{比例}
另外,程式碼到目前乍看之下好像沒問題,但我們忽略了RenderLivingEvent.Pre
事件的觸發規則:只要當客戶端的活體需要做渲染時,這個事件就可能會被觸發一次,因此,每一次世界內有動作發生 (你按了方向鍵、旋轉鏡頭、豬移動、有新的動物產生等等),都會呼叫一次改變大小一次!render.setScale()
方法沒問題,它本來就是每次渲染觸發都要重新改變;但pig.scale()
是"改變目前的碰撞箱的大小乘上parserScale值"!我們需要修改這段讓它只會被呼叫一次。
修改後的程式碼為:
private List<String> firstRenderPig = new ArrayList<String>();
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void preRender(RenderLivingEvent.Pre event) {
if (event.entity.getCustomNameTag().startsWith("myPig")) {
String tag = event.entity.getCustomNameTag();
float parserScale = Float.parseFloat(tag.split("-")[1]);
if(event.renderer instanceof RenderScalePig) {
RenderScalePig render = (RenderScalePig) event.renderer;
render.setScale(parserScale);
}
if(!firstRenderPig.contains(tag)) {
EntityScalePig pig = (EntityScalePig) event.entity;
pig.scale(parserScale);
firstRenderPig.add(tag);
}
接下來,我們需要將自定義的渲染類別註冊到Minecraft內,不然我們的EntityScalePig是沒辦法被Minecraft識別如何進行渲染:
private AtomicBoolean flag = new AtomicBoolean(false);
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void preRender(RenderLivingEvent.Pre event) {
if (event.entity.getCustomNameTag().startsWith("myPig")) {
String tag = event.entity.getCustomNameTag();
float parserScale = Float.parseFloat(tag.split("-")[1]);
if (flag.compareAndSet(false, true)) {
Minecraft.getMinecraft()
.getRenderManager()
.entityRenderMap
.put(EntityScalePig.class,
new RenderScalePig(Minecraft.getMinecraft().getRenderManager(), new ModelPig(), 0.5f * parserScale));
}
...後面省略
剩下的就是要處理我們的實體加入世界的事件了,這裡我們會完成下面的功能:
private float scale = 1.0f;
private AtomicInteger count = new AtomicInteger(0);
@SubscribeEvent
public void pigDoll(EntityJoinWorldEvent event) {
if (event.entity.worldObj.isRemote) return;
if (event.world.playerEntities.isEmpty()) return;
EntityPlayer player = (EntityPlayer) event.entity.worldObj.playerEntities.get(0);
float distance = event.entity.getDistanceToEntity(player);
if (distance < 7.0f) {
if (EntityPig.class.isAssignableFrom(event.entity.getClass())) {
if(!event.entity.getCustomNameTag().startsWith("myPig")) {
event.entity.setDead();
}
if(count.compareAndSet(5, 0)) {
scale = 1.0f;
firstRenderPig.clear();
return;
}
scale = scale * 0.8f;
EntityScalePig pig = new EntityScalePig(event.entity.worldObj);
pig.setPosition(event.entity.posX - 1.2f, event.entity.posY, event.entity.posZ);
pig.setCustomNameTag(String.format("myPig-%f", scale));
// 將豬的AI行為去除
EntityAIBase ai = new EntityAIBase() {
@Override
public boolean shouldExecute() {
return true;
}
};
ai.setMutexBits(0xFFFFFF);
pig.tasks.addTask(0, ai);
count.getAndIncrement();
event.entity.worldObj.spawnEntityInWorld(pig);
}
}
}
PigDoll_v2.java
public class PigDoll_v2 {
private float scale = 1.0f;
private AtomicInteger count = new AtomicInteger(0);
private AtomicBoolean flag = new AtomicBoolean(false);
private List<String> firstRenderPig = new ArrayList<String>();
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void preRender(RenderLivingEvent.Pre event) {
if (event.entity.getCustomNameTag().startsWith("myPig")) {
String tag = event.entity.getCustomNameTag();
float parserScale = Float.parseFloat(tag.split("-")[1]);
if (flag.compareAndSet(false, true)) {
Minecraft.getMinecraft()
.getRenderManager()
.entityRenderMap
.put(EntityScalePig.class,
new RenderScalePig(Minecraft.getMinecraft().getRenderManager(), new ModelPig(), 0.5f * parserScale));
}
if(event.renderer instanceof RenderScalePig) {
RenderScalePig render = (RenderScalePig) event.renderer;
render.setScale(parserScale);
}
if(!firstRenderPig.contains(tag)) {
EntityScalePig pig = (EntityScalePig) event.entity;
pig.scale(parserScale);
firstRenderPig.add(tag);
}
}
}
@SubscribeEvent
public void pigDoll(EntityJoinWorldEvent event) {
if (event.entity.worldObj.isRemote) return;
if (event.world.playerEntities.isEmpty()) return;
EntityPlayer player = (EntityPlayer) event.entity.worldObj.playerEntities.get(0);
float distance = event.entity.getDistanceToEntity(player);
if (distance < 7.0f) {
if (EntityPig.class.isAssignableFrom(event.entity.getClass())) {
if(!event.entity.getCustomNameTag().startsWith("myPig")) {
event.entity.setDead();
}
if(count.compareAndSet(5, 0)) {
scale = 1.0f;
firstRenderPig.clear();
return;
}
scale = scale * 0.8f;
EntityScalePig pig = new EntityScalePig(event.entity.worldObj);
pig.setPosition(event.entity.posX - 1.2f, event.entity.posY, event.entity.posZ);
pig.setCustomNameTag(String.format("myPig-%f", scale));
// 將豬的AI行為去除
EntityAIBase ai = new EntityAIBase() {
@Override
public boolean shouldExecute() {
return true;
}
};
ai.setMutexBits(0xFFFFFF);
pig.tasks.addTask(0, ai);
count.getAndIncrement();
event.entity.worldObj.spawnEntityInWorld(pig);
}
}
}
Main.java
@Mod(modid = Main.MODID, version = Main.VERSION)
public class Main {
// 作為辨識模組的唯一識別碼 (此值不可與其他模組名稱相同)
public static final String MODID = "myFancyMods";
// 模組版本號,後續可以更新
public static final String VERSION = "1.0";
@EventHandler
public void preInit(FMLPreInitializationEvent event) {
Set ids = EntityList.idToClassMapping.keySet();
int availableId = 1;
while (EntityList.getClassFromID(availableId) != null) {
availableId++;
}
EntityRegistry.registerGlobalEntityID(EntityScalePig.class, EntityScalePig.name, availableId);
}
// 告訴Forge這個是FML(Forge Mod Loader)事件處理的方法
// FMLInitializationEvent : 這裡我們處理的是模組的初始化的事件
@EventHandler
public void init(FMLInitializationEvent event) {
MinecraftForge.EVENT_BUS.register(new PigDoll_v2());
}
}
EntityScalePig.java
public class EntityScalePig extends EntityPig {
public static final String name = "MyPig";
public EntityScalePig(World worldIn) {
super(worldIn);
}
public void scale(float scale) {
this.setSize(width * scale, height * scale);
}
}
RenderScalePig.java
@SideOnly(Side.CLIENT)
public class RenderScalePig extends RenderLiving {
private static final ResourceLocation textures = new ResourceLocation("textures/entity/pig/pig.png");
private float scale = 1.0f;
public RenderScalePig(RenderManager renderManager, ModelBase modelBase, float shadowSize)
{
super(renderManager, modelBase, shadowSize);
}
public void setScale(float scale) {
this.scale = scale;
}
protected void preRenderCallback(EntityLivingBase entityLivingBase, float tickTime)
{
GlStateManager.scale(this.scale, this.scale, this.scale);
}
@Override
protected ResourceLocation getEntityTexture(Entity entity)
{
return textures;
}
}
存檔,直接透過runClient進入遊戲。拿出你的生怪蛋,找一個平坦的地方欣賞豬豬版的俄羅斯娃娃吧!
今天的內容有點多,主要是想把內容一步一步說清楚。未來我們會慢慢的把這幾天提到的功能 (自定義類別、Client與Server端的差別等)在其他主題內說明清楚與補齊,希望今天這個功能不會因此打消你想要自己打造模組的興趣